package org.batfish.datamodel;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.ImmutableMap;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import javax.annotation.Nullable;

public final class UdpPort {

  /**
   * Well-known UDP ports with names that are useful to users. Does not need to be complete, but
   * ports must be unique.
   *
   * <p>Names are autogenerated by replacing _ with - in the name, e.g., "BFD-ECHO".
   */
  private enum Values {
    BFD_CONTROL(3784),
    BFD_ECHO(3785),
    BFD_MULTIHOP(4784),
    DNS(53),
    ECHO(7),
    // HTTP/3 uses QUIC, a transport protocol on top of UDP
    HTTP(80),
    // HTTP(S)/3 uses QUIC, a transport protocol on top of UDP
    HTTPS(443),
    IMAP3(220),
    KERBEROS(750),
    POP3S(995),
    RDP(3389),
    RTSP(554),
    SNMP(161),
    SNMPTRAP(162),
    NTP(123),
    ;

    private final int _number;

    Values(int number) {
      _number = number;
    }
  }

  /** ImmutableMap rejects duplicate keys, confirming that all numbers are distinct. */
  private static final Map<Integer, UdpPort> WELL_KNOWN_PORTS =
      Arrays.stream(Values.values())
          .map(v -> new SimpleImmutableEntry<>(v._number, v.name().toUpperCase().replace('_', '-')))
          .collect(
              ImmutableMap.toImmutableMap(
                  Entry::getKey, e -> new UdpPort(e.getKey(), e.getValue())));

  public static UdpPort of(int number) {
    checkArgument(1 <= number && number <= 65535, "Invalid UDP port number %s", number);
    return WELL_KNOWN_PORTS.getOrDefault(number, new UdpPort(number, null));
  }

  public int getNumber() {
    return _number;
  }

  public Optional<String> getName() {
    return Optional.ofNullable(_name);
  }

  // Private implementation below

  private UdpPort(int number, @Nullable String name) {
    _number = number;
    _name = name;
  }

  private final int _number;
  private final @Nullable String _name;
}
